// ignore_for_file: unintended_html_in_doc_comment

import 'dart:convert';

import 'package:flutter/material.dart';
import 'package:webview_flutter/webview_flutter.dart';
import 'package:my_flutter_core/my_flutter_core.dart';

typedef GetWebViewController = void Function(WebViewController controller);

typedef WebViewContentMethodHandler = void Function(
    MethodRequest methodRequest);

class MethodRequest {
  MethodRequest({required this.name, this.params, this.callbackName});

  String name;
  Map<String, dynamic>? params;
  String? callbackName;
  // MethodCallback? callback;

  factory MethodRequest.fromJson(Map<String, dynamic> json) {
    return MethodRequest(
      name: json['name'].toString(),
      params: SharedUtil.tryToConvert<Map<String, dynamic>>(
          json['params'], (p) => p as Map<String, dynamic>),
      callbackName: json['callbackName'].toString(),
      // callback: SharedUtil.tryToConvert<MethodCallback>(json['callback'],
      //     (p) => MethodCallback.fromJson(p as Map<String, dynamic>)),
    );
  }
}

// class MethodCallback {
//   MethodCallback({required this.name, this.paramNames});

//   String name;
//   List<String>? paramNames;

//   factory MethodCallback.fromJson(Map<String, dynamic> json) {
//     return MethodCallback(
//       name: json['name'].toString(),
//       paramNames: SharedUtil.tryToConvert<List<String>>(
//           json['paramNames'], (p) => List<String>.from(p)),
//     );
//   }
// }

/// Provides a regular web view page with features like app bar, progress indicator, back action and so on.
///
/// [titleContent] is string will be displayed in a [Text]. If you want full customized title in app bar you can
/// provide [title] widget. If both provided the [title] takes the priority.
///
/// [webViewUserAgent] is the user agent will be used by UserActiveToken in backend. The default value is Flutter/WebView.
/// Different user agent means different user device.
/// That makes it possible that user can access the system at the same time from different device, like PC or mobile devices.
/// If we want to share the token with Flutter we have to set it as the same as Flutter's.
///
/// [getWebViewController] is a callback to give you the [WebViewController] instance.
/// It can be used to reload the page, to call evaluateJavascript method and so on.
///
/// [methodHandler] to handle the web page's posted message to call the Dart code.
///
/// [javaScriptChannelName] is the name that the web page in the web view can use to call the Dart code. The default value is WebContainer.
/// The sample of the web page code, WebContainer.postMessage().
///
/// From javascript in [WebView] calls WebContainer.postMessage(JSON.stringify({name: 'pickImage', params: {id: 1, name: 'Name'}})) to trigger this channel.
/// Here we are demonstrating how the H5 interacts with Flutter.
/// - H5 calls Flutter method: WebContainer.postMessage(JSON.stringify({name: 'view', params: {id: 1, name: 'Name'}})).
/// - Flutter receives the message and parses it: json.decode(data.message) as Map<String, dynamic>.
/// - Flutter does its job based on the requested method. Here is picking image: ImagePicker.pickImage.
/// - Flutter calls H5 function which MUST be defined on window object to return the result: _controller.runJavaScript('setImage("${image.path}")').
///
/// Full dart example
/// ----------------------------------------------------------------------------
/// final result = {
///   'option': {
///     "xAxis": {
///       "type": 'category',
///       "data": ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun']
///     },
///     "yAxis": {"type": 'value'},
///     "series": [
///       {
///         "data": [150, 230, 224, 218, 135, 147, 260],
///         "type": 'line'
///       }
///     ],
///   },
///   'list': [66, 45, 753, 21, 90, 21],
/// };
///
/// _controller.runJavaScript('dartCallback(${json.encode(result)})');
/// ----------------------------------------------------------------------------
///
/// web-container-service.ts:
/// ----------------------------------------------------------------------------
/// import MethodRequest from "../models/method-request";
///
/// declare const WebContainer: any;
///
/// export default class WebContainerService {
///   static invokeNativeMethod(methodRequest: MethodRequest): void {
///     WebContainer.postMessage(JSON.stringify(methodRequest));
///   }
/// }
/// ----------------------------------------------------------------------------
///
/// home-service.ts
/// ----------------------------------------------------------------------------
/// import WebContainerService from "../shared/services/web-container-service";
///
/// declare const window: any;
///
/// export default class HomeService {
///   static async getOption(params: any): Promise<any> {
///     const callbackName = 'setOption';
///
///     return new Promise((reslove, reject) => {
///       WebContainerService.invokeNativeMethod({
///         name: 'getOption',
///         params: params,
///         callbackName: callbackName
///       });
///
///       window[callbackName] = function (result: any) {
///         return reslove(result);
///       }
///     });
///   }
/// }
/// ----------------------------------------------------------------------------
class MyWebView extends StatefulWidget {
  const MyWebView({
    super.key,
    this.titleContent = '',
    this.webViewUserAgent = 'Flutter/WebView',
    this.javaScriptChannelName = 'WebContainer',
    this.fullView = true,
    this.needAppBar = true,
    this.needProgressIndicator = true,
    this.url,
    this.assetPath,
    this.htmlString,
    this.title,
    this.getWebViewController,
    this.methodHandler,
    this.onPageFinished,
  }) : assert(url != null || assetPath != null || htmlString != null);

  final String titleContent;
  final String webViewUserAgent;
  final String javaScriptChannelName;
  final bool fullView;
  final bool needAppBar;
  final bool needProgressIndicator;

  final String? url;
  final String? assetPath;
  final String? htmlString;
  final Widget? title;
  final GetWebViewController? getWebViewController;
  final WebViewContentMethodHandler? methodHandler;
  final void Function(String url)? onPageFinished;

  @override
  State<MyWebView> createState() => _MyWebViewState();
}

class _MyWebViewState extends State<MyWebView> {
  bool _isPageLoading = true;

  UniqueKey? _key;

  late String _titleContent = widget.titleContent;
  late WebViewController _controller;

  @override
  void initState() {
    super.initState();

    _key = UniqueKey();
    _initWebViewController();
  }

  @override
  Widget build(BuildContext context) {
    final webview = WebViewWidget(
      key: _key,
      controller: _controller,
    );

    if (!widget.fullView) {
      return webview;
    }

    return Scaffold(
      appBar: widget.needAppBar
          ? AppBar(
              backgroundColor: const Color.fromRGBO(250, 250, 250, 1.0),
              elevation: 0.0,
              leading: IconButton(
                icon: const Icon(Icons.navigate_before),
                color: Colors.grey,
                iconSize: 33.0,
                onPressed: _goBackOrPop,
              ),
              title: widget.title ??
                  Text(
                    _titleContent,
                    style: MyFontStyle.appBarSubtitleStyle
                        .copyWith(color: Colors.black),
                  ),
              actions: [
                IconButton(
                  icon: const Icon(Icons.refresh),
                  color: Colors.grey,
                  onPressed: () {
                    setState(() {
                      _isPageLoading = true;
                    });

                    _controller.reload();
                  },
                ),
              ],
            )
          : null,
      body: PopScope(
        // [onPopInvoked] will not be triggered by [Navigator.pop] method.
        // It's triggered by navigation button or navigation gesture.
        canPop: false,
        onPopInvokedWithResult: _onPopInvokedWithResult,
        child: Column(
          mainAxisAlignment: MainAxisAlignment.start,
          children: [
            _isPageLoading && widget.needProgressIndicator
                ? const MyLinearProgressIndicator()
                : Container(),
            Expanded(child: webview),
          ],
        ),
      ),
    );
  }

  void _initWebViewController() {
    _controller = WebViewController()
      ..enableZoom(false)
      ..setJavaScriptMode(JavaScriptMode.unrestricted)
      ..setUserAgent(widget.webViewUserAgent)
      ..setNavigationDelegate(
        NavigationDelegate(
          onProgress: (int progress) {
            // Update loading bar.
          },
          onPageStarted: (String url) {
            if (mounted) {
              setState(() {
                // When navigating between pages in [WebView] we need to display the progress indicator if needed.
                _isPageLoading = true;
              });
            }
          },
          onPageFinished: (String url) {
            if (mounted) {
              setState(() => _isPageLoading = false);
            }

            // Sets title based on loaded page if needed.
            _initTitle();

            widget.onPageFinished?.call(url);
          },
          onWebResourceError: (WebResourceError error) {
            UserInteraction.showToast('资源加载失败');
          },
        ),
      )
      ..addJavaScriptChannel(
        widget.javaScriptChannelName,
        onMessageReceived: (data) {
          // Gets the data from H5 page message.
          final jsonData = json.decode(data.message) as Map<String, dynamic>;

          // Parses the json data to [MethodRequest].
          final methodRequest = MethodRequest.fromJson(jsonData);

          // Checks if the [methodHandler] exists and if it does it will be executed.
          // We also can put some default handlers here to process some common method requests.
          widget.methodHandler?.call(methodRequest);
        },
      );

    if (!SharedUtil.checkNullOrEmpty(widget.url)) {
      _controller.loadRequest(Uri.parse(widget.url!));
    } else if (!SharedUtil.checkNullOrEmpty(widget.assetPath)) {
      _controller.loadFlutterAsset(widget.assetPath!);
    } else if (!SharedUtil.checkNullOrEmpty(widget.htmlString)) {
      _controller.loadHtmlString(widget.htmlString!);
    }

    widget.getWebViewController?.call(_controller);
  }

  Future<void> _initTitle() async {
    if (widget.title == null && _titleContent.isEmpty) {
      final title = await _controller.getTitle() ?? '';

      if (mounted) {
        setState(() => _titleContent = title);
      }
    }
  }

  void _onPopInvokedWithResult(bool didPop, dynamic result) {
    if (didPop) {
      return;
    }

    _goBackOrPop();
  }

  void _goBackOrPop() {
    // If current web can go history back.
    _controller.canGoBack().then((p) {
      if (p == true) {
        _controller.goBack();
      } else if (mounted) {
        NavigatorHelper.pop(context);
      }
    });
  }
}
